// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <map>
#include <string>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin/proto/coin_order_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"
#include "coin2/exchange/base/feed_util/kline.h"
#include "coin2/exchange/base/market/enums.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::bit::feed_v1 {
namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;
using coin2::exchange::base::feed::FeedUpdateManager;
using namespace coin::proto;


inline coin2::exchange::feed::TradeSide GetTradeSide(std::string_view side) {
  if (side == "buy") {
    return coin2::exchange::feed::TradeSide::TRADE_BUY_SIDE;
  } else if (side == "sell") {
    return coin2::exchange::feed::TradeSide::TRADE_SELL_SIDE;
  } else {
    return coin2::exchange::feed::TradeSide::TRADE_UNKNOWN_SIDE;
  }
}


class BitFeedUpdateParser {
 public:
  static void ParseSnapshot(
      BookBuilder* book,
      const rapidjson::Value& msg,
      int64_t ts) {
    auto tx = book->NewTransaction(ts);
    tx.Clear();
    for (const auto& pq : msg["asks"].GetArray()) {
      tx.UpdateAsk(stod(pq[0].GetString()), stod(pq[1].GetString()));
    }
    for (const auto& pq : msg["bids"].GetArray()) {
      tx.UpdateBid(stod(pq[0].GetString()), stod(pq[1].GetString()));
    }
  }

  static void ParseUpdate(
      BookBuilder* book,
      const rapidjson::Value& msg,
      int64_t ts) {
    auto tx = book->NewTransaction(ts);
    for (const auto& changes : msg["changes"].GetArray()) {
      const auto& items = changes.GetArray();
      if (std::string_view(items[0].GetString()) == "sell") {
        tx.UpdateAsk(stod(items[1].GetString()), stod(items[2].GetString()));
      } else if (std::string_view(items[0].GetString()) == "buy") {
        tx.UpdateBid(stod(items[1].GetString()), stod(items[2].GetString()));
      } else {
        THROW() << "Unknow side " << items[0].GetString();
      }
    }
  }

  static void ParseBbo(
      BookBuilder* book,
      const rapidjson::Value& msg,
      int64_t ts) {
    auto tx = book->NewTransaction(ts);
    tx.Clear();
    std::string_view best_ask = msg["best_ask"].GetString();
    std::string_view best_ask_qty = msg["best_ask_qty"].GetString();
    if (!best_ask.empty()) {
      tx.UpdateAsk(
          stod(best_ask),
          stod(best_ask_qty));
    }
    std::string_view best_bid = msg["best_bid"].GetString();
    std::string_view best_bid_qty = msg["best_bid_qty"].GetString();
    if (!best_bid.empty()) {
      tx.UpdateBid(
          stod(best_bid),
          stod(best_bid_qty));
    }
  }

  static void ParseTrade(
      const rapidjson::Value& msg,
      Trade* trade,
      int64_t timestamp) {
    trade->timestamp = timestamp;
    trade->fill_qty = stod(msg["qty"].GetString());
    trade->price = stod(msg["price"].GetString());
    trade->side = GetTradeSide(msg["side"].GetString());
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(std::string_view interval) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"3", fastfeed::proto::KLINE_INTERVAL_3MIN},
      {"5", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"15", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"30", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"60", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"1d", fastfeed::proto::KLINE_INTERVAL_1DAY}
    };

    auto [iter, succ] = interval_map.emplace(interval, fastfeed::proto::KLINE_INTERVAL_INVALID);
    return iter->second;
  }

  static bool ParseKlineRest(
      const rapidjson::Value& msg,
      Klines* klines,
      fastfeed::proto::KlineInterval interval,
      int64_t timestamp) {
    auto timestamps = msg["timestamps"].GetArray();
    auto open = msg["open"].GetArray();
    auto high = msg["high"].GetArray();
    auto low = msg["low"].GetArray();
    auto close = msg["close"].GetArray();
    auto volume = msg["volume"].GetArray();
    bool to_publish = false;
    for (int i = 0; i < timestamps.Size(); i++) {
      int64_t open_timestamp = stoll(timestamps[i].GetString()) * 1'000'000LL;
      auto result = klines->AddKline(open_timestamp);
      if (!result) return false;

      Kline* kline;
      bool freezed;
      std::tie(kline, freezed) = result.value();
      to_publish = to_publish || freezed;
      kline->open_timestamp = open_timestamp;
      kline->timestamp = timestamp;
      kline->interval = interval;
      kline->open = stod(open[i].GetString());
      kline->high = stod(high[i].GetString());
      kline->low = stod(low[i].GetString());
      kline->close = stod(close[i].GetString()) ;
      kline->volume = stod(volume[i].GetString()) ;
    }

    return to_publish;
  }
};

}  // namespace impl

using impl::BitFeedUpdateParser;

}  // namespace coin2::exchange::bit::feed_v1
